use std::ffi::{c_void, c_char, c_double, c_long, c_int};

use libc::{ptrdiff_t, size_t};

use crate::ffiutils::CBoolean;

pub type KFunction = Option<extern "C" fn(StatePtr, i32, ptrdiff_t) -> i32>;
pub type CFunction = extern "C" fn(StatePtr) -> i32;
pub type StatePtr = *mut c_void;
pub type Number = c_double;
pub type Integer = c_long;


#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ThreadStatus {
    Ok = 0,
    Yield,
    ErrRun,
    ErrSyntax,
    ErrMem,
    ErrErr,
}

#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ValueType {
    None = -1,
    Nil = 0,
    Boolean,
    LightUserdata,
    Number,
    String,
    Table,
    Function,
    Userdata,
    Thread
}


macro_rules! lua_method {
    ($($f: ident $(($($p: ident : $pt: ty),*))? $(:$t: ty)?);+;) => {
        $(pub fn $f(LUA_STATE_: StatePtr $(,$($p : $pt),*)?) $(-> $t)?;)+
    };
}

extern "C" {
    pub fn luaL_newstate() -> StatePtr;
    lua_method! {
        luaL_loadstring(src: *const c_char): ThreadStatus;
        luaopen_base: c_int;
        luaopen_coroutine: c_int;
        luaopen_table: c_int;
        luaopen_io: c_int;
        luaopen_os: c_int;
        luaopen_string: c_int;
        luaopen_utf8: c_int;
        luaopen_math: c_int;
        luaopen_debug: c_int;
        luaopen_package: c_int;
        luaL_openlibs;
        lua_close;
        lua_checkstack(n: c_int): c_int;
        lua_newuserdatauv(size: size_t, nuvalue: c_int): *mut c_void;
        lua_pushvalue(idx: c_int);
        lua_pushnil;
        lua_pushboolean(b: c_int);
        lua_pushnumber(n: Number);
        lua_pushinteger(i: Integer);
        lua_pushstring(s: *const c_char): *const c_char;
        lua_pushlstring(s: *const c_char, len: c_int): *const c_char;
        lua_pushcclosure(f: CFunction, nupvals: c_int);
        lua_pushlightuserdata(userdata: *mut c_void);
        lua_pushthread: c_int;
        lua_tonumberx(idx: c_int, isnum: *mut CBoolean): Number;
        lua_tointegerx(idx: c_int, isnum: *mut CBoolean): Integer;
        lua_toboolean(idx: c_int): CBoolean;
        lua_touserdata(idx: c_int): *mut c_void;
        lua_pcallk(nargs: c_int, nresults: c_int, errfunc: c_int, ctx: ptrdiff_t, k: KFunction): ThreadStatus;
        lua_getglobal(name: *const c_char): ValueType;
        lua_setglobal(name: *const c_char);
        lua_settop(idx: c_int);
        lua_gettop(): c_int;
        lua_rotate(idx: c_int, n: c_int);
        lua_type(idx: c_int): ValueType;
        lua_typename(t: ValueType): *const c_char;
    }
}
